﻿using IronRuby.Builtins;
using IronRuby.Runtime;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace MapMaker.Engine.Model.VXAce
{
    public abstract class RPG
    {
        private static Dictionary<Type, Func<object, object>> CONVERTERS = new Dictionary<Type, Func<object,object>>();
        static RPG()
        {
            CONVERTERS.Add(typeof(string), o => string.Format("{0}", o));
            CONVERTERS.Add(typeof(int?), o => o == null ? (int?) null : (int?) int.Parse(string.Format("{0}",o)));
        }

        protected static void Each(dynamic model, Action<dynamic> action)
        {
            if (model == null) return;
            if (model is IEnumerable)
            {
                foreach (dynamic d in model)
                {
                    try
                    {
                        if (d == null) continue;
                    }
                    catch 
                    {
                        string s = string.Format("{0}", d);
                        if (s.Length == 0)
                            continue;
                    }
                    action(d);
                }
            }
            else
            {
                action(model);
            }
        }

        protected static bool Try(Action action)
        {
            try
            {
                action();
                return true;
            }
            catch(Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Try action failed: " + ex.Message);
                return false;
            }
        }


        protected static T Convert<T>(Func<object> src)
        {
            Type t = typeof(T);
            return (T)ConvertType(t, src());
        }

        protected static object ConvertType(Type t, object o)
        {
            try
            {
                if (CONVERTERS.ContainsKey(t))
                    return CONVERTERS[t](o);
                else
                {
                    if (t == typeof(Object))
                        return o;
                    return System.Convert.ChangeType(o, t);
                }
            }
            catch(Exception ex) 
            {
                throw;
            }
        }

       

        internal static IEnumerable<T> Import<T>(ScriptEngine engine, dynamic o) where T : new()
        {
            List<T> set = new List<T>();
            Action<dynamic> del = (Action<dynamic>)(m => Import<T>(engine, set, m));
            Each(o, del);
            return set;
        }

        private static void Import<T>(ScriptEngine engine, List<T> set, dynamic o) where T : new()
        {
            set.Add(Convert<T>(engine, o));
        }

        internal static T Convert<T>(ScriptEngine engine, dynamic bean) where T : new()
        {
            Func<object> create = (() => new T());
            return (T) Convert(typeof(T), engine, bean, create);
        }

        internal static object Convert(Type t, ScriptEngine engine, dynamic bean, Func<object> create) 
        {
            object item = create();
            int objectKey = -1;
            if (t == typeof(RPG_MapInfo) || t == typeof(RPG_Event))
            {
                KeyValuePair<object, object> hash = new KeyValuePair<object, object>();
                if (Try(() => hash = ((KeyValuePair<object, object>)bean)))
                {
                    objectKey = (int)System.Convert.ChangeType(hash.Key, typeof(int));
                    bean = hash.Value;
                }
            }
            
            foreach (PropertyInfo prop in t.GetProperties())
            {
                object[] attribs = prop.GetCustomAttributes(typeof(RubyProperty), true);
                if (attribs == null || attribs.Length == 0) continue;
                RubyProperty ruby = (RubyProperty)attribs[0];

                var operations = engine.CreateOperations();
                dynamic d = operations.GetMember(bean, ruby.Name);
                if (d == null) continue;

                if (d is IronRuby.Builtins.MutableString || d is IronRuby.Builtins.ClrString)
                {
                    Try(() => prop.SetValue(item, ConvertType(prop.PropertyType, d), null));
                }
                else
                {
                    if (prop.PropertyType == typeof(object))
                    {
                       System.Diagnostics.Debugger.Break();
                       continue;
                    }                
                    if (d is IEnumerable)
                    {
                        if (prop.PropertyType.IsArray)
                        {
                            Type elementType = prop.PropertyType.GetElementType();
                            RubyArray arry = (RubyArray)d;
                            Array target = Array.CreateInstance(elementType, (int)arry.Count);
                            int i = 0;
                            foreach(dynamic a in d)
                            {
                                Try(() => target.SetValue(ConvertType(elementType, a), i));
                                i++;
                            }
                            Try(() => prop.SetValue(item, target, null));
                        }
                        else
                        {
                            MethodInfo method = typeof(RPG).GetMethod("ImportList", BindingFlags.Static | BindingFlags.NonPublic);
                            if(prop.PropertyType.GetGenericArguments().Length == 0)
                                System.Diagnostics.Debugger.Break();
                            Type genType = prop.PropertyType.GetGenericArguments()[0];
                            method = method.MakeGenericMethod(new Type[] { genType });
                            object list = method.Invoke(null, new object[] { engine, d });
                            Try(() => prop.SetValue(item, list, null));
                        }
                    }
                    else
                    {
                        if (prop.PropertyType.Assembly.Equals(typeof(RPG).Assembly))
                        {
                            MethodInfo method = typeof(RPG).GetMethod("InnerConvert", BindingFlags.Static | BindingFlags.NonPublic);
                            Type genType = prop.PropertyType;
                            method = method.MakeGenericMethod(new Type[] { genType });
                            object ret = method.Invoke(null, new object[] { engine, d });
                            Try(() => prop.SetValue(item, ret, null));
                        }
                        else 
                        {
                            Try(() => prop.SetValue(item, ConvertType(prop.PropertyType, d), null));
                        }
                    }
                }
            }
            return item;
        }

        private static List<T> ImportList<T>(ScriptEngine engine, dynamic o) where T : new()
        {
            List<T> set = new List<T>();
            Action<dynamic> del = (Action<dynamic>)(m => Import<T>(engine, set, m));
            Each(o, del);
            return set;
        }

        private static T InnerConvert<T>(ScriptEngine engine, dynamic bean) where T : new()
        {
            Func<object> create = (() => new T());
            return (T)Convert(typeof(T), engine, bean, create);
        }

    }
}
